package middleware;

import java.io.*;

import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;

import java.util.Properties;
import java.util.ArrayList;
import java.util.StringTokenizer;

import logging.Logging;

// Core Class of MiddlewareServer
public class MiddlewareServer {
		
		//client stuff
		private ArrayList<Connection> connections;
		//private ArrayList<String> urls = new ArrayList<String>();
		//private ArrayList<Properties> connProperties = new ArrayList<Properties>();
		private Properties properties = new Properties();
		
		//db stuff
		private ArrayList<String> dbHosts;
		private ArrayList<Integer> dbPorts;
		private ArrayList<String> dbUsernames;
		private ArrayList<String> dbPasswords;
		private ArrayList<String> dbNames;
		private String dbMode;
		
		// THE QUUUUUUEUE
		/**
		 * @uml.property  name="queue"
		 * @uml.associationEnd  
		 */
		public Queue queue = new Queue();

		
		/**
		 * Creates input/output streams and reads config.properties.
		 */
		MiddlewareServer () {
			
			FileInputStream iStream;
			try {
				iStream  = new FileInputStream("config/config.properties");
				properties.load(iStream);
				iStream.close();
			} catch (FileNotFoundException e) {
				System.err.println("File not found: " + e.getMessage());
			} catch (IOException e) {
				System.err.println("IO error: " + e.getMessage());
			}
			
			//set all the Hosts
			String hostsString = properties.getProperty("dbHost");
			dbHosts = new ArrayList<String>();
			StringTokenizer st = new StringTokenizer(hostsString, ",");
			while (st.hasMoreTokens()) {
				dbHosts.add(st.nextToken().trim());
			}
			
			// set all Ports
			String portsString = properties.getProperty("dbPortNr");
			dbPorts = new ArrayList<Integer>();
			st = new StringTokenizer(portsString, ",");
			while (st.hasMoreTokens()) {
				dbPorts.add(new Integer(st.nextToken().trim()));
			}
			
			// set the db names
			String dbNamesString = properties.getProperty("dbName");
			dbNames = new ArrayList<String>();
			st = new StringTokenizer(dbNamesString, ",");
			while (st.hasMoreTokens()) {
				dbNames.add(st.nextToken().trim());
			}
			
			// set the usernames
			String usernamesString = properties.getProperty("dbUser");
			dbUsernames = new ArrayList<String>();
			st = new StringTokenizer(usernamesString, ",");
			while (st.hasMoreTokens()) {
				dbUsernames.add(st.nextToken().trim());
			}
			
			String passwordsString = properties.getProperty("dbPassword");
			dbPasswords = new ArrayList<String>();
			st = new StringTokenizer(passwordsString, ",");
			while (st.hasMoreTokens()) {
				dbPasswords.add(st.nextToken().trim());
			}
			
			dbMode = properties.getProperty("dbMode").trim();
			
			Logging.openMiddlewareWriter();
		}
		
		/**
		 * Establishes all connections to the databases.
		 * One persistent connection for each db server.
		 */
		public void makeConnections () {
			//load driver
			try {
				Class.forName( "org.postgresql.Driver");
			} catch (ClassNotFoundException e) {
				System.err.println("Error with driver: " + e.getMessage());
			}
			
			String st;
			Properties props;
			connections = new ArrayList<Connection>();
			for (int i = 0; i <  dbHosts.size(); i++) {
				// constructing the connection-string
				st = new String("jdbc:postgresql:");
				st += dbHosts.get(i) + ":";
				st += dbPorts.get(i) + "/";
				st += dbNames.get(i);
				
				props = new Properties();
				props.setProperty("user", dbUsernames.get(i));
				
				if (dbPasswords.size() > 0) {
					props.setProperty("password", dbPasswords.get(i));
				} else {
					props.setProperty("password", "");
				}
				
				try {
					Connection conn = DriverManager.getConnection(st, props);
					connections.add(conn);
//					properties.add(props);
//					urls.add(st);
				} catch (Exception e) {
					System.err.println("Cannot establish connection:" + e.getMessage());
				}
			}
		}
		
		/**
		 * Terminates all connections.
		 */
		public void closeConnections() {
			for (int i = 0; i < connections.size(); i++) {
				try {
					connections.get(i).close();
				} catch (SQLException e) {
					System.err.println("Couldn't close connection: " + e.getMessage());
					
				}
			}
			
		}
		

		/**
		 * Runs the middleware server - core routine of architecture.
		 * Creates listener, db logic.
		 */
		private void run() {
			this.makeConnections();
			
			// create thread to listen and accept client connections
			Listener listener = new Listener(queue);
			listener.start();
			
			// check dbMode, i.e. whether full replication or sharding database needed
			// corresponding classes are created for the logic.
			AbstractDatabaseLogic dbLogic = null;
			if (dbMode.equalsIgnoreCase("fullReplication")) {
				dbLogic = new FullReplicationDatabaseLogic(connections, queue);				
			} else if (dbMode.equalsIgnoreCase("sharding")) {
				dbLogic = new ShardingDatabaseLogic(connections, queue);				
			} else {
				System.err.println("de modus kenni noed");
				System.exit(1);
			}
			
			dbLogic.start();
			
			
			// wait until all connections are closed
			boolean clientStarted = false;
			while (Thread.activeCount() > 3 || !clientStarted) {
				Logging.logQueueSize(queue.size());
				try {
					Thread.sleep(10000);
				} catch (InterruptedException e) {}
				
				if (Thread.activeCount() > 3) {
					clientStarted = true;
				}
			}
			
			this.closeConnections();
			Logging.closeWriter();
			System.exit(1);
		}
		
		
		public static void main(String[] args) {

			MiddlewareServer middlewareServer = new MiddlewareServer();
			middlewareServer.run();			
		
		}
	}

